<?php
/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package    Zend_Form
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */

/**
 * Zend_Form_DisplayGroup
 *
 * @category   Zend
 * @package    Zend_Form
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 * @version    $Id: DisplayGroup.php 23871 2011-04-23 22:40:16Z ramon $
 */
class Zend_Form_DisplayGroup implements Iterator, Countable {
	/**
	 * Group attributes
	 * @var array
	 */
	protected $_attribs = array ();
	
	/**
	 * Display group decorators
	 * @var array
	 */
	protected $_decorators = array ();
	
	/**
	 * Description
	 * @var string
	 */
	protected $_description;
	
	/**
	 * Should we disable loading the default decorators?
	 * @var bool
	 */
	protected $_disableLoadDefaultDecorators = false;
	
	/**
	 * Element order
	 * @var array
	 */
	protected $_elementOrder = array ();
	
	/**
	 * Elements
	 * @var array
	 */
	protected $_elements = array ();
	
	/**
	 * Form object to which the display group is currently registered
	 *
	 * @var Zend_Form
	 */
	protected $_form;
	
	/**
	 * Whether or not a new element has been added to the group
	 * @var bool
	 */
	protected $_groupUpdated = false;
	
	/**
	 * Plugin loader for decorators
	 * @var Zend_Loader_PluginLoader
	 */
	protected $_loader;
	
	/**
	 * Group name
	 * @var string
	 */
	protected $_name;
	
	/**
	 * Group order
	 * @var int
	 */
	protected $_order;
	
	/**
	 * @var Zend_Translate
	 */
	protected $_translator;
	
	/**
	 * Is translation disabled?
	 * @var bool
	 */
	protected $_translatorDisabled = false;
	
	/**
	 * @var Zend_View_Interface
	 */
	protected $_view;
	
	/**
	 * Constructor
	 *
	 * @param  string $name
	 * @param  Zend_Loader_PluginLoader $loader
	 * @param  array|Zend_Config $options
	 * @return void
	 */
	public function __construct($name, Zend_Loader_PluginLoader $loader, $options = null) {
		$this->setName ( $name );
		
		$this->setPluginLoader ( $loader );
		
		if (is_array ( $options )) {
			$this->setOptions ( $options );
		} elseif ($options instanceof Zend_Config) {
			$this->setConfig ( $options );
		}
		
		// Extensions...
		$this->init ();
		
		$this->loadDefaultDecorators ();
	}
	
	/**
	 * Initialize object; used by extending classes
	 *
	 * @return void
	 */
	public function init() {
	}
	
	/**
	 * Set options
	 *
	 * @param  array $options
	 * @return Zend_Form_DisplayGroup
	 */
	public function setOptions(array $options) {
		$forbidden = array ('Options', 'Config', 'PluginLoader', 'View', 'Translator', 'Attrib' );
		foreach ( $options as $key => $value ) {
			$normalized = ucfirst ( $key );
			
			if (in_array ( $normalized, $forbidden )) {
				continue;
			}
			
			$method = 'set' . $normalized;
			if (method_exists ( $this, $method )) {
				$this->$method ( $value );
			} else {
				$this->setAttrib ( $key, $value );
			}
		}
		return $this;
	}
	
	/**
	 * Set options from config object
	 *
	 * @param  Zend_Config $config
	 * @return Zend_Form_DisplayGroup
	 */
	public function setConfig(Zend_Config $config) {
		return $this->setOptions ( $config->toArray () );
	}
	
	/**
	 * Set group attribute
	 *
	 * @param  string $key
	 * @param  mixed $value
	 * @return Zend_Form_DisplayGroup
	 */
	public function setAttrib($key, $value) {
		$key = ( string ) $key;
		$this->_attribs [$key] = $value;
		return $this;
	}
	
	/**
	 * Add multiple form attributes at once
	 *
	 * @param  array $attribs
	 * @return Zend_Form_DisplayGroup
	 */
	public function addAttribs(array $attribs) {
		foreach ( $attribs as $key => $value ) {
			$this->setAttrib ( $key, $value );
		}
		return $this;
	}
	
	/**
	 * Set multiple form attributes at once
	 *
	 * Overwrites any previously set attributes.
	 *
	 * @param  array $attribs
	 * @return Zend_Form_DisplayGroup
	 */
	public function setAttribs(array $attribs) {
		$this->clearAttribs ();
		return $this->addAttribs ( $attribs );
	}
	
	/**
	 * Retrieve a single form attribute
	 *
	 * @param  string $key
	 * @return mixed
	 */
	public function getAttrib($key) {
		$key = ( string ) $key;
		if (! isset ( $this->_attribs [$key] )) {
			return null;
		}
		
		return $this->_attribs [$key];
	}
	
	/**
	 * Retrieve all form attributes/metadata
	 *
	 * @return array
	 */
	public function getAttribs() {
		return $this->_attribs;
	}
	
	/**
	 * Remove attribute
	 *
	 * @param  string $key
	 * @return bool
	 */
	public function removeAttrib($key) {
		if (array_key_exists ( $key, $this->_attribs )) {
			unset ( $this->_attribs [$key] );
			return true;
		}
		
		return false;
	}
	
	/**
	 * Clear all form attributes
	 *
	 * @return Zend_Form
	 */
	public function clearAttribs() {
		$this->_attribs = array ();
		return $this;
	}
	
	/**
	 * Set form object to which the display group is attached
	 *
	 * @param  Zend_Form $form
	 * @return Zend_Form_DisplayGroup
	 */
	public function setForm(Zend_Form $form) {
		$this->_form = $form;
		
		// Ensure any elements attached prior to setting the form are now
		// removed from iteration by the form
		foreach ( $this->getElements () as $element ) {
			$form->removeFromIteration ( $element->getName () );
		}
		
		return $this;
	}
	
	/**
	 * Get form object to which the group is attached
	 *
	 * @return Zend_Form|null
	 */
	public function getForm() {
		return $this->_form;
	}
	
	/**
	 * Filter a name to only allow valid variable characters
	 *
	 * @param  string $value
	 * @return string
	 */
	public function filterName($value) {
		return preg_replace ( '/[^a-zA-Z0-9_\x7f-\xff]/', '', ( string ) $value );
	}
	
	/**
	 * Set group name
	 *
	 * @param  string $name
	 * @return Zend_Form_DisplayGroup
	 */
	public function setName($name) {
		$name = $this->filtername ( $name );
		if (('0' !== $name) && empty ( $name )) {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception ( 'Invalid name provided; must contain only valid variable characters and be non-empty' );
		}
		
		$this->_name = $name;
		return $this;
	}
	
	/**
	 * Retrieve group name
	 *
	 * @return string
	 */
	public function getName() {
		return $this->_name;
	}
	
	/**
	 * Get fully qualified name
	 *
	 * Places name as subitem of array and/or appends brackets.
	 *
	 * @return string
	 */
	public function getFullyQualifiedName() {
		return $this->getName ();
	}
	
	/**
	 * Get element id
	 *
	 * @return string
	 */
	public function getId() {
		if (isset ( $this->id )) {
			return $this->id;
		}
		
		$id = $this->getFullyQualifiedName ();
		
		// Bail early if no array notation detected
		if (! strstr ( $id, '[' )) {
			return $id;
		}
		
		// Strip array notation
		if ('[]' == substr ( $id, - 2 )) {
			$id = substr ( $id, 0, strlen ( $id ) - 2 );
		}
		$id = str_replace ( '][', '-', $id );
		$id = str_replace ( array (']', '[' ), '-', $id );
		$id = trim ( $id, '-' );
		
		return $id;
	}
	
	/**
	 * Set group legend
	 *
	 * @param  string $legend
	 * @return Zend_Form_DisplayGroup
	 */
	public function setLegend($legend) {
		return $this->setAttrib ( 'legend', ( string ) $legend );
	}
	
	/**
	 * Retrieve group legend
	 *
	 * @return string
	 */
	public function getLegend() {
		return $this->getAttrib ( 'legend' );
	}
	
	/**
	 * Set description
	 *
	 * @param  string $value
	 * @return Zend_Form_DisplayGroup
	 */
	public function setDescription($value) {
		$this->_description = ( string ) $value;
		return $this;
	}
	
	/**
	 * Get description
	 *
	 * @return string
	 */
	public function getDescription() {
		return $this->_description;
	}
	
	/**
	 * Set group order
	 *
	 * @param  int $order
	 * @return Zend_Form_Element
	 */
	public function setOrder($order) {
		$this->_order = ( int ) $order;
		return $this;
	}
	
	/**
	 * Retrieve group order
	 *
	 * @return int
	 */
	public function getOrder() {
		return $this->_order;
	}
	
	// Elements
	

	/**
	 * Add element to stack
	 *
	 * @param  Zend_Form_Element $element
	 * @return Zend_Form_DisplayGroup
	 */
	public function addElement(Zend_Form_Element $element) {
		$this->_elements [$element->getName ()] = $element;
		$this->_groupUpdated = true;
		
		// Display group will now handle display of element
		if (null !== ($form = $this->getForm ())) {
			$form->removeFromIteration ( $element->getName () );
		}
		
		return $this;
	}
	
	/**
	 * Add multiple elements at once
	 *
	 * @param  array $elements
	 * @return Zend_Form_DisplayGroup
	 * @throws Zend_Form_Exception if any element is not a Zend_Form_Element
	 */
	public function addElements(array $elements) {
		foreach ( $elements as $element ) {
			if (! $element instanceof Zend_Form_Element) {
				require_once 'Zend/Form/Exception.php';
				throw new Zend_Form_Exception ( 'elements passed via array to addElements() must be Zend_Form_Elements only' );
			}
			$this->addElement ( $element );
		}
		return $this;
	}
	
	/**
	 * Set multiple elements at once (overwrites)
	 *
	 * @param  array $elements
	 * @return Zend_Form_DisplayGroup
	 */
	public function setElements(array $elements) {
		$this->clearElements ();
		return $this->addElements ( $elements );
	}
	
	/**
	 * Retrieve element
	 *
	 * @param  string $name
	 * @return Zend_Form_Element|null
	 */
	public function getElement($name) {
		$name = ( string ) $name;
		if (isset ( $this->_elements [$name] )) {
			return $this->_elements [$name];
		}
		
		return null;
	}
	
	/**
	 * Retrieve elements
	 * @return array
	 */
	public function getElements() {
		return $this->_elements;
	}
	
	/**
	 * Remove a single element
	 *
	 * @param  string $name
	 * @return boolean
	 */
	public function removeElement($name) {
		$name = ( string ) $name;
		if (array_key_exists ( $name, $this->_elements )) {
			unset ( $this->_elements [$name] );
			$this->_groupUpdated = true;
			return true;
		}
		
		return false;
	}
	
	/**
	 * Remove all elements
	 *
	 * @return Zend_Form_DisplayGroup
	 */
	public function clearElements() {
		$this->_elements = array ();
		$this->_groupUpdated = true;
		return $this;
	}
	
	// Plugin loader (for decorators)
	

	/**
	 * Set plugin loader
	 *
	 * @param  Zend_Loader_PluginLoader $loader
	 * @return Zend_Form_DisplayGroup
	 */
	public function setPluginLoader(Zend_Loader_PluginLoader $loader) {
		$this->_loader = $loader;
		return $this;
	}
	
	/**
	 * Retrieve plugin loader
	 *
	 * @return Zend_Loader_PluginLoader
	 */
	public function getPluginLoader() {
		return $this->_loader;
	}
	
	/**
	 * Add a prefix path for the plugin loader
	 *
	 * @param  string $prefix
	 * @param  string $path
	 * @return Zend_Form_DisplayGroup
	 */
	public function addPrefixPath($prefix, $path) {
		$this->getPluginLoader ()->addPrefixPath ( $prefix, $path );
		return $this;
	}
	
	/**
	 * Add several prefix paths at once
	 *
	 * @param  array $spec
	 * @return Zend_Form_DisplayGroup
	 */
	public function addPrefixPaths(array $spec) {
		if (isset ( $spec ['prefix'] ) && isset ( $spec ['path'] )) {
			return $this->addPrefixPath ( $spec ['prefix'], $spec ['path'] );
		}
		foreach ( $spec as $prefix => $paths ) {
			if (is_numeric ( $prefix ) && is_array ( $paths )) {
				$prefix = null;
				if (isset ( $paths ['prefix'] ) && isset ( $paths ['path'] )) {
					$this->addPrefixPath ( $paths ['prefix'], $paths ['path'] );
				}
			} elseif (! is_numeric ( $prefix )) {
				if (is_string ( $paths )) {
					$this->addPrefixPath ( $prefix, $paths );
				} elseif (is_array ( $paths )) {
					foreach ( $paths as $path ) {
						$this->addPrefixPath ( $prefix, $path );
					}
				}
			}
		}
		return $this;
	}
	
	// Decorators
	

	/**
	 * Set flag to disable loading default decorators
	 *
	 * @param  bool $flag
	 * @return Zend_Form_Element
	 */
	public function setDisableLoadDefaultDecorators($flag) {
		$this->_disableLoadDefaultDecorators = ( bool ) $flag;
		return $this;
	}
	
	/**
	 * Should we load the default decorators?
	 *
	 * @return bool
	 */
	public function loadDefaultDecoratorsIsDisabled() {
		return $this->_disableLoadDefaultDecorators;
	}
	
	/**
	 * Load default decorators
	 *
	 * @return Zend_Form_DisplayGroup
	 */
	public function loadDefaultDecorators() {
		if ($this->loadDefaultDecoratorsIsDisabled ()) {
			return $this;
		}
		
		$decorators = $this->getDecorators ();
		if (empty ( $decorators )) {
			$this->addDecorator ( 'FormElements' )->addDecorator ( 'HtmlTag', array ('tag' => 'dl' ) )->addDecorator ( 'Fieldset' )->addDecorator ( 'DtDdWrapper' );
		}
		return $this;
	}
	
	/**
	 * Instantiate a decorator based on class name or class name fragment
	 *
	 * @param  string $name
	 * @param  null|array $options
	 * @return Zend_Form_Decorator_Interface
	 */
	protected function _getDecorator($name, $options = null) {
		$class = $this->getPluginLoader ()->load ( $name );
		if (null === $options) {
			$decorator = new $class ();
		} else {
			$decorator = new $class ( $options );
		}
		
		return $decorator;
	}
	
	/**
	 * Add a decorator for rendering the group
	 *
	 * @param  string|Zend_Form_Decorator_Interface $decorator
	 * @param  array|Zend_Config $options Options with which to initialize decorator
	 * @return Zend_Form_DisplayGroup
	 */
	public function addDecorator($decorator, $options = null) {
		if ($decorator instanceof Zend_Form_Decorator_Interface) {
			$name = get_class ( $decorator );
		} elseif (is_string ( $decorator )) {
			$name = $decorator;
			$decorator = array ('decorator' => $name, 'options' => $options );
		} elseif (is_array ( $decorator )) {
			foreach ( $decorator as $name => $spec ) {
				break;
			}
			if (is_numeric ( $name )) {
				require_once 'Zend/Form/Exception.php';
				throw new Zend_Form_Exception ( 'Invalid alias provided to addDecorator; must be alphanumeric string' );
			}
			if (is_string ( $spec )) {
				$decorator = array ('decorator' => $spec, 'options' => $options );
			} elseif ($spec instanceof Zend_Form_Decorator_Interface) {
				$decorator = $spec;
			}
		} else {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception ( 'Invalid decorator provided to addDecorator; must be string or Zend_Form_Decorator_Interface' );
		}
		
		$this->_decorators [$name] = $decorator;
		
		return $this;
	}
	
	/**
	 * Add many decorators at once
	 *
	 * @param  array $decorators
	 * @return Zend_Form_DisplayGroup
	 */
	public function addDecorators(array $decorators) {
		foreach ( $decorators as $decoratorName => $decoratorInfo ) {
			if (is_string ( $decoratorInfo ) || $decoratorInfo instanceof Zend_Form_Decorator_Interface) {
				if (! is_numeric ( $decoratorName )) {
					$this->addDecorator ( array ($decoratorName => $decoratorInfo ) );
				} else {
					$this->addDecorator ( $decoratorInfo );
				}
			} elseif (is_array ( $decoratorInfo )) {
				$argc = count ( $decoratorInfo );
				$options = array ();
				if (isset ( $decoratorInfo ['decorator'] )) {
					$decorator = $decoratorInfo ['decorator'];
					if (isset ( $decoratorInfo ['options'] )) {
						$options = $decoratorInfo ['options'];
					}
					$this->addDecorator ( $decorator, $options );
				} else {
					switch (true) {
						case (0 == $argc) :
							break;
						case (1 <= $argc) :
							$decorator = array_shift ( $decoratorInfo );
						case (2 <= $argc) :
							$options = array_shift ( $decoratorInfo );
						default :
							$this->addDecorator ( $decorator, $options );
							break;
					}
				}
			} else {
				require_once 'Zend/Form/Exception.php';
				throw new Zend_Form_Exception ( 'Invalid decorator passed to addDecorators()' );
			}
		}
		
		return $this;
	}
	
	/**
	 * Overwrite all decorators
	 *
	 * @param  array $decorators
	 * @return Zend_Form_DisplayGroup
	 */
	public function setDecorators(array $decorators) {
		$this->clearDecorators ();
		return $this->addDecorators ( $decorators );
	}
	
	/**
	 * Retrieve a registered decorator
	 *
	 * @param  string $name
	 * @return false|Zend_Form_Decorator_Abstract
	 */
	public function getDecorator($name) {
		if (! isset ( $this->_decorators [$name] )) {
			$len = strlen ( $name );
			foreach ( $this->_decorators as $localName => $decorator ) {
				if ($len > strlen ( $localName )) {
					continue;
				}
				
				if (0 === substr_compare ( $localName, $name, - $len, $len, true )) {
					if (is_array ( $decorator )) {
						return $this->_loadDecorator ( $decorator, $localName );
					}
					return $decorator;
				}
			}
			return false;
		}
		
		if (is_array ( $this->_decorators [$name] )) {
			return $this->_loadDecorator ( $this->_decorators [$name], $name );
		}
		
		return $this->_decorators [$name];
	}
	
	/**
	 * Retrieve all decorators
	 *
	 * @return array
	 */
	public function getDecorators() {
		foreach ( $this->_decorators as $key => $value ) {
			if (is_array ( $value )) {
				$this->_loadDecorator ( $value, $key );
			}
		}
		return $this->_decorators;
	}
	
	/**
	 * Remove a single decorator
	 *
	 * @param  string $name
	 * @return bool
	 */
	public function removeDecorator($name) {
		$decorator = $this->getDecorator ( $name );
		if ($decorator) {
			if (array_key_exists ( $name, $this->_decorators )) {
				unset ( $this->_decorators [$name] );
			} else {
				$class = get_class ( $decorator );
				unset ( $this->_decorators [$class] );
			}
			return true;
		}
		
		return false;
	}
	
	/**
	 * Clear all decorators
	 *
	 * @return Zend_Form_DisplayGroup
	 */
	public function clearDecorators() {
		$this->_decorators = array ();
		return $this;
	}
	
	/**
	 * Set view
	 *
	 * @param  Zend_View_Interface $view
	 * @return Zend_Form_DisplayGroup
	 */
	public function setView(Zend_View_Interface $view = null) {
		$this->_view = $view;
		return $this;
	}
	
	/**
	 * Retrieve view
	 *
	 * @return Zend_View_Interface
	 */
	public function getView() {
		if (null === $this->_view) {
			require_once 'Zend/Controller/Action/HelperBroker.php';
			$viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper ( 'viewRenderer' );
			$this->setView ( $viewRenderer->view );
		}
		
		return $this->_view;
	}
	
	/**
	 * Render display group
	 *
	 * @return string
	 */
	public function render(Zend_View_Interface $view = null) {
		if (null !== $view) {
			$this->setView ( $view );
		}
		$content = '';
		foreach ( $this->getDecorators () as $decorator ) {
			$decorator->setElement ( $this );
			$content = $decorator->render ( $content );
		}
		return $content;
	}
	
	/**
	 * String representation of group
	 *
	 * @return string
	 */
	public function __toString() {
		try {
			$return = $this->render ();
			return $return;
		} catch ( Exception $e ) {
			trigger_error ( $e->getMessage (), E_USER_WARNING );
			return '';
		}
	}
	
	/**
	 * Set translator object
	 *
	 * @param  Zend_Translate|Zend_Translate_Adapter|null $translator
	 * @return Zend_Form_DisplayGroup
	 */
	public function setTranslator($translator = null) {
		if ((null === $translator) || ($translator instanceof Zend_Translate_Adapter)) {
			$this->_translator = $translator;
		} elseif ($translator instanceof Zend_Translate) {
			$this->_translator = $translator->getAdapter ();
		} else {
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception ( 'Invalid translator specified' );
		}
		return $this;
	}
	
	/**
	 * Retrieve translator object
	 *
	 * @return Zend_Translate_Adapter|null
	 */
	public function getTranslator() {
		if ($this->translatorIsDisabled ()) {
			return null;
		}
		
		if (null === $this->_translator) {
			require_once 'Zend/Form.php';
			return Zend_Form::getDefaultTranslator ();
		}
		
		return $this->_translator;
	}
	
	/**
	 * Indicate whether or not translation should be disabled
	 *
	 * @param  bool $flag
	 * @return Zend_Form_DisplayGroup
	 */
	public function setDisableTranslator($flag) {
		$this->_translatorDisabled = ( bool ) $flag;
		return $this;
	}
	
	/**
	 * Is translation disabled?
	 *
	 * @return bool
	 */
	public function translatorIsDisabled() {
		return $this->_translatorDisabled;
	}
	
	/**
	 * Overloading: allow rendering specific decorators
	 *
	 * Call renderDecoratorName() to render a specific decorator.
	 *
	 * @param  string $method
	 * @param  array $args
	 * @return string
	 * @throws Zend_Form_Exception for invalid decorator or invalid method call
	 */
	public function __call($method, $args) {
		if ('render' == substr ( $method, 0, 6 )) {
			$decoratorName = substr ( $method, 6 );
			if (false !== ($decorator = $this->getDecorator ( $decoratorName ))) {
				$decorator->setElement ( $this );
				$seed = '';
				if (0 < count ( $args )) {
					$seed = array_shift ( $args );
				}
				return $decorator->render ( $seed );
			}
			
			require_once 'Zend/Form/Exception.php';
			throw new Zend_Form_Exception ( sprintf ( 'Decorator by name %s does not exist', $decoratorName ) );
		}
		
		require_once 'Zend/Form/Exception.php';
		throw new Zend_Form_Exception ( sprintf ( 'Method %s does not exist', $method ) );
	}
	
	// Interfaces: Iterator, Countable
	

	/**
	 * Current element
	 *
	 * @return Zend_Form_Element
	 */
	public function current() {
		$this->_sort ();
		current ( $this->_elementOrder );
		$key = key ( $this->_elementOrder );
		return $this->getElement ( $key );
	}
	
	/**
	 * Current element
	 *
	 * @return string
	 */
	public function key() {
		$this->_sort ();
		return key ( $this->_elementOrder );
	}
	
	/**
	 * Move pointer to next element
	 *
	 * @return void
	 */
	public function next() {
		$this->_sort ();
		next ( $this->_elementOrder );
	}
	
	/**
	 * Move pointer to beginning of element loop
	 *
	 * @return void
	 */
	public function rewind() {
		$this->_sort ();
		reset ( $this->_elementOrder );
	}
	
	/**
	 * Determine if current element/subform/display group is valid
	 *
	 * @return bool
	 */
	public function valid() {
		$this->_sort ();
		return (current ( $this->_elementOrder ) !== false);
	}
	
	/**
	 * Count of elements/subforms that are iterable
	 *
	 * @return int
	 */
	public function count() {
		return count ( $this->_elements );
	}
	
	/**
	 * Sort items according to their order
	 *
	 * @return void
	 */
	protected function _sort() {
		if ($this->_groupUpdated || ! is_array ( $this->_elementOrder )) {
			$elementOrder = array ();
			foreach ( $this->getElements () as $key => $element ) {
				$elementOrder [$key] = $element->getOrder ();
			}
			
			$items = array ();
			$index = 0;
			foreach ( $elementOrder as $key => $order ) {
				if (null === $order) {
					while ( array_search ( $index, $elementOrder, true ) ) {
						++ $index;
					}
					$items [$index] = $key;
					++ $index;
				} else {
					$items [$order] = $key;
				}
			}
			
			$items = array_flip ( $items );
			asort ( $items );
			$this->_elementOrder = $items;
			$this->_groupUpdated = false;
		}
	}
	
	/**
	 * Lazy-load a decorator
	 *
	 * @param  array $decorator Decorator type and options
	 * @param  mixed $name Decorator name or alias
	 * @return Zend_Form_Decorator_Interface
	 */
	protected function _loadDecorator(array $decorator, $name) {
		$sameName = false;
		if ($name == $decorator ['decorator']) {
			$sameName = true;
		}
		
		$instance = $this->_getDecorator ( $decorator ['decorator'], $decorator ['options'] );
		if ($sameName) {
			$newName = get_class ( $instance );
			$decoratorNames = array_keys ( $this->_decorators );
			$order = array_flip ( $decoratorNames );
			$order [$newName] = $order [$name];
			$decoratorsExchange = array ();
			unset ( $order [$name] );
			asort ( $order );
			foreach ( $order as $key => $index ) {
				if ($key == $newName) {
					$decoratorsExchange [$key] = $instance;
					continue;
				}
				$decoratorsExchange [$key] = $this->_decorators [$key];
			}
			$this->_decorators = $decoratorsExchange;
		} else {
			$this->_decorators [$name] = $instance;
		}
		
		return $instance;
	}
}
